// copyright 1993 Michael B. Johnson; some portions copyright 1994, MIT
// see COPYRIGHT for reuse legalities
//


#import "WWInterp.h"
#import "EveVarTypeInfo.h"

#define PI    (3.1415926535897932384626433)
#define DtoR  (PI/180.0)
#define RtoD  (180.0/PI)


// for the routines I'm using written by Darwyn Peachey
// from the book:
// "Texturing and Modeling: A Procedural Approach"
//   by Ebert, Musgrave, Peachey, Perlin, and Worley
//   ISBN 0-12-228761-4
#import "proctext.h"


@implementation WWInterp


+ initialize { return [WWInterp setVersion:1], self; }

// need to get rid of this!
static char errBuf[1024];

///////////////////////////////
///// math functions //////////
///////////////////////////////



static int 
exprCmd_pi(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr)
{
  // pi takes no arguments; returns 
  // it returns a double corresponding to the converted value in radians
  resultPtr->type = TCL_DOUBLE;
  resultPtr->doubleValue = (double)3.1415926535897932384626433;
  return TCL_OK;
}

static int 
exprCmd_radians(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr)
{
  // radians takes one argument; a value expressed in degrees
  // it returns a double corresponding to the converted value in radians
  resultPtr->type = TCL_DOUBLE;
  if (args->type == TCL_INT)
  {  resultPtr->doubleValue = (double)(args->intValue) * DtoR;
  }
  else
  {  resultPtr->doubleValue = args->doubleValue * DtoR;
  }
  return TCL_OK;
}

static int 
exprCmd_degrees(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr)
{
  // degrees takes one argument; a value expressed in radians
  // it returns a double corresponding to the converted value in degrees
  resultPtr->type = TCL_DOUBLE;
  if (args->type == TCL_INT)
  {  resultPtr->doubleValue = (double)(args->intValue) * RtoD;
  }
  else
  {  resultPtr->doubleValue = args->doubleValue * RtoD;
  }
  return TCL_OK;
}

static int 
exprCmd_sign(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr)
{
  // sign takes one argument
  // it returns a double : -1 if the value is negative, 0 if zero, and +1 is the value is positive
  resultPtr->type = TCL_INT;
  if (args->type == TCL_INT)
  {  if (args->intValue == 0)
     {  resultPtr->intValue = 0;
     }
     else
     {  if (args->intValue < 0)
        {  resultPtr->intValue = -1;
        }
        else
        {  resultPtr->intValue = 1;
        }
     }
  }
  else
  {  if (args->doubleValue == 0.0)
     {  resultPtr->intValue = 0;
     }
     else
     {  if (args->doubleValue < 0.0)
        {  resultPtr->intValue = -1;
        }
        else
        {  resultPtr->intValue = 1;
        }
     }
  }
  return TCL_OK;
}

static int 
exprCmd_min(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr)
{
  // min takes two arguments
  // it returns a value corresponding to minimum of the two
  if (args[0].type == TCL_INT)
  {  if (args[1].type == TCL_INT)  // both args are ints
     {  resultPtr->type = TCL_INT;
        if (args[0].intValue < args[1].intValue)
        {  resultPtr->intValue = args[0].intValue;
        }
        else
        {  resultPtr->intValue = args[1].intValue;
        }
     }
     else  // the first arg is an int, the second is a double
     {  resultPtr->type = TCL_DOUBLE;
        if (args[0].intValue < args[1].doubleValue)
        {  resultPtr->doubleValue = (double)(args[0].intValue);
        }
        else
        {  resultPtr->doubleValue = args[1].doubleValue;
        }
     }
  }
  else  // the first arg is a double
  {  if (args[1].type == TCL_DOUBLE)  // both args are doubles
     {  resultPtr->type = TCL_DOUBLE;
        if (args[0].doubleValue < args[1].doubleValue)
        {  resultPtr->doubleValue = args[0].doubleValue;
        }
        else
        {  resultPtr->doubleValue = args[1].doubleValue;
        }
     }
     else  // the first arg is a double, the second is an int
     {  resultPtr->type = TCL_DOUBLE;
        if (args[0].doubleValue < args[1].intValue)
        {  resultPtr->doubleValue = args[0].doubleValue;
        }
        else
        {  resultPtr->intValue = (double)(args[1].intValue);
        }
     }
  }

  return TCL_OK;
}

static int 
exprCmd_max(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr)
{
  // max takes two arguments
  // it returns a value corresponding to maximum of the two
  if (args[0].type == TCL_INT)
  {  if (args[1].type == TCL_INT)  // both args are ints
     {  resultPtr->type = TCL_INT;
        if (args[0].intValue > args[1].intValue)
        {  resultPtr->intValue = args[0].intValue;
        }
        else
        {  resultPtr->intValue = args[1].intValue;
        }
     }
     else  // the first arg is an int, the second is a double
     {  resultPtr->type = TCL_DOUBLE;
        if (args[0].intValue > args[1].doubleValue)
        {  resultPtr->doubleValue = (double)(args[0].intValue);
        }
        else
        {  resultPtr->doubleValue = args[1].doubleValue;
        }
     }
  }
  else  // the first arg is a double
  {  if (args[1].type == TCL_DOUBLE)  // both args are doubles
     {  resultPtr->type = TCL_DOUBLE;
        if (args[0].doubleValue > args[1].doubleValue)
        {  resultPtr->doubleValue = args[0].doubleValue;
        }
        else
        {  resultPtr->doubleValue = args[1].doubleValue;
        }
     }
     else  // the first arg is a double, the second is an int
     {  resultPtr->type = TCL_DOUBLE;
        if (args[0].doubleValue > args[1].intValue)
        {  resultPtr->doubleValue = args[0].doubleValue;
        }
        else
        {  resultPtr->intValue = (double)(args[1].intValue);
        }
     }
  }

  return TCL_OK;
}


static int 
exprCmd_clamp(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr)
{
  // clamp takes three float arguments, a, min, max
  // it returns a value clamped between min and max
  resultPtr->type = TCL_DOUBLE;
  if (args[0].doubleValue < args[1].doubleValue)
  {  resultPtr->doubleValue = args[1].doubleValue;
  }
  else
  {  if (args[0].doubleValue > args[2].doubleValue)
     {  resultPtr->doubleValue = args[2].doubleValue;
     }
     else
     {  resultPtr->doubleValue = args[0].doubleValue;
     }
  }
  return TCL_OK;
}

static int 
exprCmd_step(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr)
{
  // clamp takes two float arguments: min, value
  // if value is less than min, it returns 0, otherwise it returns 1
  resultPtr->type = TCL_DOUBLE;
  if (args[1].doubleValue < args[0].doubleValue)
  {  resultPtr->doubleValue = 0.0;
  }
  else
  {  resultPtr->doubleValue = 1.0;
  }
  return TCL_OK;
}


static int 
exprCmd_spline(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr)
{
  float  vec[4];


  // spline takes 5 arguments; the first is the point along u (0 -> 1) and the 
  // other 4 are control points
  // it smoothly interpolates between them
  resultPtr->type = TCL_DOUBLE;
  vec[0] = args[1].doubleValue;
  vec[1] = args[2].doubleValue;
  vec[2] = args[3].doubleValue;
  vec[3] = args[4].doubleValue;
  resultPtr->doubleValue = spline(args[0].doubleValue, 4, vec);
  return TCL_OK;
}


static int 
exprCmd_smoothstep(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr)
{
  // smoothstep takes three float arguments: min, max, value
  // if value is less than min, it returns min, if it's above max, it returns max, otherwise
  // it smoothly interpolates between them
  resultPtr->type = TCL_DOUBLE;
  resultPtr->doubleValue = smoothstep(args[0].doubleValue, args[1].doubleValue, args[2].doubleValue);
  return TCL_OK;
}


static int 
exprCmd_lerpDown(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr)
{
  // lerpDown takes three float arguments: u, min, max
  // if u is less than or equal 0, it returns max, if it's above or equal 1, it returns min, otherwise
  // it smoothly interpolates between them, downwards
  resultPtr->type = TCL_DOUBLE;
  if (args[0].doubleValue <= 0.0)
  {  resultPtr->doubleValue = args[2].doubleValue;
  }
  else
  {  if (args[0].doubleValue >= 1.0)
     {  resultPtr->doubleValue = args[1].doubleValue;
     }
     else
     {  resultPtr->doubleValue = args[2].doubleValue; // give it a base
        resultPtr->doubleValue -= args[0].doubleValue * (args[2].doubleValue - args[1].doubleValue);
     }
  }
  return TCL_OK;
}


static int 
exprCmd_lerpUp(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr)
{
  // lerpUp takes three float arguments: u, min, max
  // if u is less than 0, it returns min, if it's above 1, it returns max, otherwise
  // it smoothly interpolates between them
  resultPtr->type = TCL_DOUBLE;
  if (args[0].doubleValue <= 0.0)
  {  resultPtr->doubleValue = args[1].doubleValue;
  }
  else
  {  if (args[0].doubleValue >= 1.0)
     {  resultPtr->doubleValue = args[2].doubleValue;
     }
     else
     {  resultPtr->doubleValue = args[1].doubleValue; // give it a base
        resultPtr->doubleValue += args[0].doubleValue * (args[2].doubleValue - args[1].doubleValue);
     }
  }
  return TCL_OK;
}


static int 
exprCmd_noise(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr)
{
  // noise takes three float arguments: x, y, and z
  // it returns some value between 0 and 1 which is a random function of its argument
  resultPtr->type = TCL_DOUBLE;
  resultPtr->doubleValue = (double)gnoise((float)(args[0].doubleValue), (float)(args[1].doubleValue), (float)(args[2].doubleValue));

  return TCL_OK;
}

static int 
exprCmd_gvnoise(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr)
{
  // gvnoise takes three float arguments: x, y, and z
  // it returns some value between 0 and 1 which is a random function of its argument
  resultPtr->type = TCL_DOUBLE;
  resultPtr->doubleValue = (double)gvnoise((float)(args[0].doubleValue), (float)(args[1].doubleValue), (float)(args[2].doubleValue));

  return TCL_OK;
}


static int 
exprCmd_scnoise(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr)
{
  // scoise takes three float arguments: x, y, and z
  // it returns some value between 0 and 1 which is a random function of its argument
  resultPtr->type = TCL_DOUBLE;
  resultPtr->doubleValue = (double)scnoise((float)(args[0].doubleValue), (float)(args[1].doubleValue), (float)(args[2].doubleValue));

  return TCL_OK;
}


static int 
exprCmd_vnoise(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr)
{
  // scoise takes three float arguments: x, y, and z
  // it returns some value between 0 and 1 which is a random function of its argument
  resultPtr->type = TCL_DOUBLE;
  resultPtr->doubleValue = (double)vnoise((float)(args[0].doubleValue), (float)(args[1].doubleValue), (float)(args[2].doubleValue));

  return TCL_OK;
}


static int 
exprCmd_vcnoise(ClientData clientData, Tcl_Interp *interp, Tcl_Value *args, Tcl_Value *resultPtr)
{
  // scoise takes three float arguments: x, y, and z
  // it returns some value between 0 and 1 which is a random function of its argument
  resultPtr->type = TCL_DOUBLE;
  resultPtr->doubleValue = (double)vcnoise((float)(args[0].doubleValue), (float)(args[1].doubleValue), (float)(args[2].doubleValue));

  return TCL_OK;
}


///////////////////////////////
///// math commands ///////////
///////////////////////////////


// the following routines are more flexible versions of some of the above math functions
static int
cmd_spline(WWInterp *me, Tcl_Interp *interp, int argc, char **argv)
{
  char    *my_args = "u rank {knot0 knot1 knot2 ... knotN}";
  char    **argv2;
  int     argc2, i, j, rank, nKnots, knotArgIndex;
  float   u, *knots, *retVector, *knotPtr;
  double  dU, dKnot;


  if (argc != 4) // need to allow non-list knots...
  {  sprintf(errBuf, "USAGE: %s %s (not right number of args: need at least 4 knots (remember the knots should be a single arg - enclose them in quotes)", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  if (Tcl_GetDouble(interp, argv[1], &dU) != TCL_OK)
  {  sprintf(errBuf, "USAGE: %s %s (1st arg not a valid float)", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }
  u = (float)dU;

  if (Tcl_GetInt(interp, argv[2], &rank) != TCL_OK)
  {  sprintf(errBuf, "USAGE: %s %s (2nd arg not a valid integer)", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  if (argc == 4) // assuming second arg is actually a list; need to split it
  {  Tcl_SplitList(interp, argv[3], &argc2, &argv2);
     // the knot vector must be at least (rank * 4)
     if (argc2 < (rank * 4))
     {  sprintf(errBuf, 
		"USAGE: %s %s (after splitting 3rd arg still not enough args: need at least 4 knots)", 
		argv[0], my_args);
        Tcl_AppendResult(interp, errBuf, (char *)NULL);
        return TCL_ERROR;
     }
     if (argc2 % rank)
     {  sprintf(errBuf, 
		"USAGE: %s %s (after splitting 3rd arg, number of elements wasn't evenly divisible by %d)", 
		argv[0], my_args, rank);
        Tcl_AppendResult(interp, errBuf, (char *)NULL);
        return TCL_ERROR;
     }

     // okay, the number of elements in the list is correct.
     // now we need to malloc up memory for the float vector to hand spline()
     // we only need enough memory for a rank==1 vector, since we'll spline
     // each component separately.

     nKnots = argc2 / rank;
     knots = (float *)malloc(sizeof(float) * nKnots);
     retVector = (float *)malloc(sizeof(float) * rank);
     for (i = 0; i < rank; i++)
     {  knotPtr = knots;
        for (j = 0; j < nKnots; j++)
        {  knotArgIndex = (j * rank) + i;
           if (Tcl_GetDouble(interp, argv2[knotArgIndex], &dKnot) != TCL_OK)
   	   {  sprintf(errBuf, 
                      "USAGE: %s %s (vector component (%d, %d) not a valid float)", 
                      argv[0], my_args, j, i);
              Tcl_AppendResult(interp, errBuf, (char *)NULL);
              free(knots);
              free(retVector); 
             return TCL_ERROR;
           }
           *knotPtr++ = (float)dKnot;
        }
        retVector[i] = spline(u, nKnots, knots);
     }

     for (i = 0; i < rank; i++)
     {  sprintf(errBuf, "%f ", retVector[i]);
        Tcl_AppendElement(interp, errBuf);
     }
  }
  else  // should also allow non-list version...
  {}

  return TCL_OK;
}



// registerSampleGenerator
static int
cmd_registerSampleGenerator(WWInterp *me, Tcl_Interp *interp, int argc, char **argv)
{
  char    *my_args = "sampleGeneratorName weight";


  if (argc != 3)
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  if (![me registerCurrentSampleGeneratorName:(const char *)argv[1] weight:(float)atof(argv[2])])
  {  sprintf(errBuf, "unable to register sample generator %s with a weight of %f", argv[1], (float)atof(argv[2]));
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  return TCL_OK;
}


// sampleSet
static int
cmd_setSample(WWInterp *me, Tcl_Interp *interp, int argc, char **argv)
{
  char    *my_args = "varName [newValue sampleGeneratorName]";
  char    *varValue;


  if ((argc != 2) && (argc != 4))
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  if (argc == 2)
  {  varValue = [me getVar:argv[1]];
     if (!varValue)
     {  sprintf(errBuf, "can't read \"%s\": no such variable", argv[1]);
        Tcl_AppendResult(interp, errBuf, (char *)NULL);
        return TCL_ERROR;
     }     
     Tcl_AppendResult(interp, varValue, (char *)NULL);
     return TCL_OK;
  }

  [me setCurrentSampleGeneratorName:argv[3]];
  varValue = [me setVar:argv[1] toValue:argv[2]];
  if (!varValue)
  {  sprintf(errBuf, "couldn't set \"%s\" to \"%s\"", argv[1], argv[2]);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     [me setCurrentSampleGeneratorName:NULL];
     return TCL_ERROR;
  }     
  Tcl_AppendResult(interp, varValue, (char *)NULL);

  [me setCurrentSampleGeneratorName:NULL];
  return TCL_OK;
}



///////////////////////////////
///// type functions //////////
///////////////////////////////
static char 
*WriteProcForPI(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  return "PI is an eve-specific read-only variable";
}


#if 0
static char 
*WriteProcForEnumVariable(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char  *varString;


  // at this point, the new value of the variable has been written.  We need to make
  // sure that the new value is okay.  If it is, great, we write the new value into
  // the varTypeInfo object as the value.  If it isn't okay, we need to grab the old
  // value from the varTypeInfo object and reset the value of the variable.

  varString = [[(WWTCLVarTrace *)clientData tclInterp] getVar2:name1 :name2];
  if ([[(WWTCLVarTrace *)clientData datum] isThisAValidEnumValue:varString])
  {  // this is a valid value; update varTypeInfo object and get out
     [[(WWTCLVarTrace *)clientData datum] setValue:varString];
     return NULL;
  }
  else
  {  // value isn't valid; reset to old value and return error
     [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:(char *)[[(WWTCLVarTrace *)clientData datum] value]];
     return "invalid value for enumerated string variable";
  }
  return NULL;
}


// the following routines extend tcl with some rudimentary typing, which is used in eve
static int
cmd_setEnum(WWInterp *me, Tcl_Interp *interp, int argc, char **argv)
{
  char  *my_args = "varName [initialValue] [list-of-possible-values]";
  id    varTypeInfo;
  char  **argv2;
  int   argc2, i;


  if ((argc != 2) && (argc != 4))
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  // if there is only two arguments, return the enumeration list of the arg
  // if it doesn't already exist, we need to return an error
  // probably should have a hash Table of the various variables, right?
  if (argc == 2)
  {  // they just want to know the value, not set it
     if (![me enumExists:argv[1]]) 
     {  sprintf(errBuf, "can't read \"%s\": no such enumerated variable", argv[0]);
        Tcl_AppendResult(interp, errBuf, (char *)NULL);
        return TCL_ERROR;
     }

     // okay, the variable does exist as an enumerated value
     // we want to return 

     return TCL_OK;
  }


  // the first thing to do is unset this variable so that if any unset 
  // traces need to be run, they will be.
  [me unsetVar:argv[1]];
  
  // otherwise, check to see if there is an enum for this named variable
  // already, throw it away, and generate a new one.

  // this routine is pretty cool.  It constrains the values of the
  // named variable to be ones matching the list of list.  What's neat is
  // that any given element of that list can be a "regular expression".
  // All writes to this variable will checked against this new value.


  // actually, it shouldn't just alloc one up, it should ask the interp
  // for it, so the interp can see if it already has one...

  varTypeInfo = [[EveVarTypeInfo alloc] initForVar:argv[1] withInterp:me];
  [me addVarTypeInfo:varTypeInfo];
  [varTypeInfo setTypeEnum];
  Tcl_SplitList(interp, argv[2], &argc2, &argv2);
  for (i = 0; i < argc2; i++)
  {  [varTypeInfo addEnum:argv2[i]];
  }

  // need to set up a write trace for this variable, handing in the id
  // of the varTypeInfo object.  In this routine, a message needs to get
  // sent to the varTypeInfo with the value of the new value.  If it's
  // okay, the varTypeInfo object will return YES.  If it's not, it will
  // return NO.  At this point, if the new value is okay, the varTypeInfo
  // object should get sent a message setting the value to be that; otherwise
  // the old value should be gotten from there and the new value should be
  // set.
  [me traceWritesOn:argv[1] andCall:(Tcl_VarTraceProc *)WriteProcForEnumVariable usingData:(ClientData)varTypeInfo];

  return TCL_OK;
}


static char 
*WriteProcForFloatVariable(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char  *varString;


  // at this point, the new value of the variable has been written.  We need to make
  // sure that the new value is okay.  If it is, great, we write the new value into
  // the varTypeInfo object as the value.  If it isn't okay, we need to grab the old
  // value from the varTypeInfo object and reset the value of the variable.

  varString = [[(WWTCLVarTrace *)clientData tclInterp] getVar2:name1 :name2];
  if ([[(WWTCLVarTrace *)clientData datum] isThisAValidEnumValue:varString])
  {  // this is a valid value; update varTypeInfo object and get out
     [[(WWTCLVarTrace *)clientData datum] setValue:varString];
     return NULL;
  }
  else
  {  // value isn't valid; reset to old value and return error
     [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:(char *)[[(WWTCLVarTrace *)clientData datum] value]];
     return "invalid value for enumerated string variable";
  }
  return NULL;
}

// the following routines extend tcl with some rudimentary typing, which is used in eve
static int
cmd_setFloatMinMax(WWInterp *me, Tcl_Interp *interp, int argc, char **argv)
{
  char  *my_args = "varName [initialFloatValue] [floatMinValue floatMaxValue]";
  id    varTypeInfo;
  char  **argv2;
  int   argc2, i;


  if ((argc != 2) && (argc != 3) && (argc != 5))
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }
  // if there is only two arguments, return the value, min, and max of the variable
  // if it doesn't already exist, we need to return an error
  // probably should have a hash Table of the various variables, right?
  if (argc == 2)
  {  // they just want to know the value, not set it
     if (![me floatMinMaxExists:argv[1]]) 
     {  sprintf(errBuf, "can't read \"%s\": no such minMax constrained float variable", argv[0]);
        Tcl_AppendResult(interp, errBuf, (char *)NULL);
        return TCL_ERROR;
     }

     // okay, the variable does exist as a min/max float
     // we want to return cons up the current value, min, and max

     return TCL_OK;
  }


  if (argc == 3)  // new value
  {  // they just want to set a new value, not define a new one
     if (![me floatMinMaxExists:argv[1]]) 
     {  sprintf(errBuf, "can't read \"%s\": no such minMax constrained float variable", argv[0]);
        Tcl_AppendResult(interp, errBuf, (char *)NULL);
        return TCL_ERROR;
     }

     // okay, the variable does exist as a min/max float
     // we want to just set the new value and then return it.
     // we let the variable tracing routines check the bounds
     return [me setVar:argv[1] toValue:argv[2]];
  }


  // the first thing to do is unset this variable so that if any unset 
  // traces need to be run, they will be.
  [me unsetVar:argv[1]];
  
  // otherwise, check to see if there is an enum for this named variable
  // already, throw it away, and generate a new one.


  return TCL_OK;
}
#endif

static char 
*WriteProcForReadOnly(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  return "this is a read-only variable";
}


// the following routines extend tcl with some rudimentary typing, which is used in eve
static int
cmd_setReadOnly(WWInterp *me, Tcl_Interp *interp, int argc, char **argv)
{
  char  *my_args = "readOnlyVarName value";


  if (argc != 3)
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  // the first thing to do is unset this variable so that if any unset 
  // traces need to be run, they will be.
  [me unsetVar:argv[1]];
  [me setVar:argv[1] toValue:argv[2]];
  [me traceWritesOn:argv[1] andCall:(Tcl_VarTraceProc *)WriteProcForReadOnly usingData:(ClientData)me];

  // need to add it to the wwVarList, so we know that it's special...


  return TCL_OK;
}

static int
cmd_noop(WWInterp *me, Tcl_Interp *interp, int argc, char **argv)
{
  return TCL_OK;
}



- (char *)setVar:(char *)variableName toReadOnlyValue:(char *)newValue
{
  char  *ret;


  [self unsetVar:variableName];
  ret = [self setVar:variableName toValue:newValue];
  [self traceWritesOn:variableName andCall:(Tcl_VarTraceProc *)WriteProcForReadOnly usingData:(ClientData)nil];
  return ret;
}



- (char *)setVar2:(char *)variableName1 :(char *)variableName2 toReadOnlyValue:(char *)newValue
{
  char  *ret;


  [self unsetVar2:variableName1 :variableName2];
  ret = [self setVar2:variableName1 :variableName2 toValue:newValue];
  [self traceWritesOn2:variableName1 :variableName2 andCall:(Tcl_VarTraceProc *)WriteProcForReadOnly usingData:(ClientData)nil];
  return ret;
}


//////////////////////////////////////////////
////// okay, on to the instance methods //////
//////////////////////////////////////////////

- setupInterp
{
  int            i, howMany, numArgs;
  Tcl_ValueType  argTypes[10];
  char           *valAsString, *varName;


  if (interpSetup)
  {  return self;
  }

  valAsString = (char *)NXZoneCalloc([self zone], 256, sizeof(char));
  varName = (char *)NXZoneCalloc([self zone], 256, sizeof(char));

  // add the new functions to do variable typing, including their trace functions
  //[self addCommand:"setEnum" :(Tcl_CmdProc *)cmd_setEnum :self];

  [self addCommand:"setReadOnly" :(Tcl_CmdProc *)cmd_setReadOnly :self];
  [self addCommand:"spline" :(Tcl_CmdProc *)cmd_spline :self];
  [self addCommand:"setSample" :(Tcl_CmdProc *)cmd_setSample :self];
  [self addCommand:"sSet" :(Tcl_CmdProc *)cmd_setSample :self];
  [self addCommand:"setS" :(Tcl_CmdProc *)cmd_setSample :self];
  [self addCommand:"sS" :(Tcl_CmdProc *)cmd_setSample :self];
  [self addCommand:"registerSampleGenerator" :(Tcl_CmdProc *)cmd_registerSampleGenerator :self];
  [self addCommand:"sampleGeneratorIsExecuting" :(Tcl_CmdProc *)cmd_noop :self]; // need to make this do something, pal!

  // now add in the new expr math functions
  // pi()
  numArgs = 0;
  [self createMathFunc:"pi" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes 
        proc:(Tcl_MathProc *)exprCmd_pi clientData:(ClientData)self]; 
  // radians(a)
  numArgs = 1;
  argTypes[0] = TCL_DOUBLE;
  [self createMathFunc:"radians" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes 
        proc:(Tcl_MathProc *)exprCmd_radians clientData:(ClientData)self]; 
  // degrees(a)
  numArgs = 1;
  argTypes[0] = TCL_DOUBLE;
  [self createMathFunc:"degrees" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes 
        proc:(Tcl_MathProc *)exprCmd_degrees clientData:(ClientData)self]; 
  // sign(a)
  numArgs = 1;
  argTypes[0] = TCL_EITHER;
  [self createMathFunc:"sign" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes 
        proc:(Tcl_MathProc *)exprCmd_sign clientData:(ClientData)self]; 
  // min(a, b)
  numArgs = 2;
  argTypes[0] = TCL_EITHER;
  argTypes[1] = TCL_EITHER;
  [self createMathFunc:"min" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes 
        proc:(Tcl_MathProc *)exprCmd_min clientData:(ClientData)self]; 
  // max(a, b)
  numArgs = 2;
  argTypes[0] = TCL_EITHER;
  argTypes[1] = TCL_EITHER;
  [self createMathFunc:"max" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes 
        proc:(Tcl_MathProc *)exprCmd_max clientData:(ClientData)self]; 
  // clamp(a, min, max)
  numArgs = 3;
  argTypes[0] = TCL_DOUBLE;
  argTypes[1] = TCL_DOUBLE;
  argTypes[2] = TCL_DOUBLE;
  [self createMathFunc:"clamp" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes 
        proc:(Tcl_MathProc *)exprCmd_clamp clientData:(ClientData)self]; 
  // step(min, value)
  numArgs = 2;
  argTypes[0] = TCL_DOUBLE;
  argTypes[1] = TCL_DOUBLE;
  [self createMathFunc:"step" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes 
        proc:(Tcl_MathProc *)exprCmd_step clientData:(ClientData)self]; 

  // spline(u, pt0, pt1, pt2, pt3)
  numArgs = 5;
  argTypes[0] = TCL_DOUBLE;
  argTypes[1] = TCL_DOUBLE;
  argTypes[2] = TCL_DOUBLE;
  argTypes[3] = TCL_DOUBLE;
  argTypes[4] = TCL_DOUBLE;
  [self createMathFunc:"spline" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes 
        proc:(Tcl_MathProc *)exprCmd_spline clientData:(ClientData)self]; 
  // smoothstep(min, max, value)
  numArgs = 3;
  argTypes[0] = TCL_DOUBLE;
  argTypes[1] = TCL_DOUBLE;
  argTypes[2] = TCL_DOUBLE;
  [self createMathFunc:"smoothstep" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes 
        proc:(Tcl_MathProc *)exprCmd_smoothstep clientData:(ClientData)self]; 

  // lerpDown(u, min, max)
  numArgs = 3;
  argTypes[0] = TCL_DOUBLE;
  argTypes[1] = TCL_DOUBLE;
  argTypes[2] = TCL_DOUBLE;
  [self createMathFunc:"lerpDown" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes 
        proc:(Tcl_MathProc *)exprCmd_lerpDown clientData:(ClientData)self]; 

  // lerpUp(u, min, max)
  numArgs = 3;
  argTypes[0] = TCL_DOUBLE;
  argTypes[1] = TCL_DOUBLE;
  argTypes[2] = TCL_DOUBLE;
  [self createMathFunc:"lerpUp" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes 
        proc:(Tcl_MathProc *)exprCmd_lerpUp clientData:(ClientData)self]; 

  // noise(x, y, z)
  numArgs = 3;
  argTypes[0] = TCL_DOUBLE;
  argTypes[1] = TCL_DOUBLE;
  argTypes[2] = TCL_DOUBLE;
  [self createMathFunc:"noise" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes 
        proc:(Tcl_MathProc *)exprCmd_noise clientData:(ClientData)self]; 

  // gvnoise(x, y, z)
  numArgs = 3;
  argTypes[0] = TCL_DOUBLE;
  argTypes[1] = TCL_DOUBLE;
  argTypes[2] = TCL_DOUBLE;
  [self createMathFunc:"gvnoise" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes 
        proc:(Tcl_MathProc *)exprCmd_gvnoise clientData:(ClientData)self]; 

  // scnoise(x, y, z)
  numArgs = 3;
  argTypes[0] = TCL_DOUBLE;
  argTypes[1] = TCL_DOUBLE;
  argTypes[2] = TCL_DOUBLE;
  [self createMathFunc:"scnoise" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes 
        proc:(Tcl_MathProc *)exprCmd_scnoise clientData:(ClientData)self]; 

  // vnoise(x, y, z)
  numArgs = 3;
  argTypes[0] = TCL_DOUBLE;
  argTypes[1] = TCL_DOUBLE;
  argTypes[2] = TCL_DOUBLE;
  [self createMathFunc:"vnoise" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes 
        proc:(Tcl_MathProc *)exprCmd_vnoise clientData:(ClientData)self]; 

  // vcnoise(x, y, z)
  numArgs = 3;
  argTypes[0] = TCL_DOUBLE;
  argTypes[1] = TCL_DOUBLE;
  argTypes[2] = TCL_DOUBLE;
  [self createMathFunc:"vcnoise" numArgs:numArgs argTypes:(Tcl_ValueType *)argTypes 
        proc:(Tcl_MathProc *)exprCmd_vcnoise clientData:(ClientData)self]; 

  // now put any read only constants into the environment (like PI)
  strcpy(varName, "PI");
  sprintf(valAsString, "%f", PI); // turn the value into its tcl equivalent
  [self setVar:varName toValue:valAsString];
  [self traceWritesOn:varName andCall:(Tcl_VarTraceProc *)WriteProcForPI usingData:(ClientData)self];
  // wave NOTE: need to make this un-unsettable with an unset trace...


  // need to grovel over the typed variables and reset the traces.
  howMany = [wwVarList count];
  for (i = 0; i < howMany; i++)
  {  // for each variable, reset the traces appropriately
     // I really should have probably done this in read:
     NXLogError("wave is lame\n");
  }

  interpSetup = YES;

  NXZoneFree([self zone], valAsString);
  NXZoneFree([self zone], varName);

  return self;
}


- init
{
  [super init];

  wwVarList = [[List alloc] init];
  interpSetup = NO;

  [self setupInterp];

  return self;
}


- awake
{
  [super awake];

  interpSetup = NO;

  [self setupInterp];

  return self;
}


- setRunTime:obj { runTime = obj; return self; }

- registerCurrentSampleGeneratorName:(const char *)newCurrentSampleGeneratorName weight:(float)weight  
{ 
   return [runTime registerCurrentSampleGeneratorName:newCurrentSampleGeneratorName weight:weight];
}  


- setCurrentSampleGeneratorName:(const char *)newCurrentSampleGeneratorName
{
   return [runTime setCurrentSampleGeneratorName:newCurrentSampleGeneratorName];
}

- addVarTypeInfo:newVarTypeInfo
{
  [wwVarList addObject:newVarTypeInfo];
  return self;
}

- (BOOL)enumExists:(const char *)enumVarName
{
  // this method gets called when defining a new enumerated string value
  // the idea is to check and see if an enumerated variable for this
  // guy already exists.  Note this routine doesn't check to see if
  // there is any other type of variable already defined that has
  // this name

  return YES;
}

- (int)unsetVar:(char *)variableName 
{
  // first need to check and see if this variable is "special".  
  // If so, do the appropriate activity to get rid of it.

  return [super unsetVar:variableName];
}


// we need to deal with a few extra things here...
// need to think about "sets" that happened in the context of a EveCmd
// need to deal with typed variables (which, in a sense, a set inside an EveCmd is)

- writeState:(NXStream *)stream 
{
   int          i, j, argc, argc2, ret, cnt;
   char         **argv, **argv2, *varNameList, *procNameList, *aCmd, *varValue, *arrayElementList, *arrayName;
   Tcl_DString  setCmd, infoCmd, procCmd, arrayElementCmd;
   BOOL         skip;


   // at this point, we need to suck out the names and values of all
   // the global variables in the tcl interp.  Then we need to get 
   // the names, args, and bodies of all the procs that have been 
   // defined.  We write these out, and then when we do a read:, 
   // we shove those back into the interp.

   // okay, so first we send "info globals" to the interp.  
   // We'll get back a string which we'll do a Tcl_SplitList on.
   // we'll write out the argc of that list to the stream.
   // for each element in the string, we'll stick "set " in front of it 
   // and evaluate it.  We'll then save the string "set <variable> <value>"
   // out to the stream.

   // we'll then send "info procs" to the interp.
   // We'll get back a string which we'll do a Tcl_SplitList on.
   // we'll write out the argc of that list to the stream.
   // for each element in the string, we'll call that element "procName"
   // for each procName, we'll then send "info args <procName>" and call the
   // result "procArgs",  we'll then send "info body <procName>" and call the
   // the result "procBody",
   // we'll then sprintf into a string variable: 
   // sprintf(procDef, "proc %s {%s} {%s}", procName, procArgs, procBody);
   // and save "procDef" out to the stream

   ret = Tcl_GlobalEval(interp, "info globals"); 
   if (ret != TCL_OK)
   {  NXLogError("Yikes! problem in WWTCLInterp's write: (evaluating <info globals> yielded <%s>)\n", 
	         interp->result);
      [self showError:interp->result];
      varNameList = NXCopyStringBuffer("");
   }
   else
   {  varNameList = NXCopyStringBuffer(interp->result); 
   }
   Tcl_ResetResult(interp); 

   argc = 0;
   argv = NULL;
   Tcl_SplitList(interp, varNameList, &argc, &argv);
   Tcl_ResetResult(interp); 

   cnt = 0;
   for (i = 0; i < argc; i++)
   {  skip = NO;
      if (!strcmp(argv[i], "env")) {  skip = YES;  }
      if (!strcmp(argv[i], "PI")) {  skip = YES;  }
      if (!strcmp(argv[i], "ticksPerSecond")) {  skip = YES;  }
      if (!strcmp(argv[i], "errorInfo")) {  skip = YES;  }
      if (!strcmp(argv[i], "scene")) {  skip = YES;  }
      if (!skip)
      {  varValue = Tcl_GetVar(interp, argv[i], TCL_GLOBAL_ONLY);
         if (varValue)
         {  cnt++; 
         }
         else
         {  Tcl_DStringInit(&arrayElementCmd);
            Tcl_DStringAppend(&arrayElementCmd, "array names ", -1);
            aCmd = Tcl_DStringAppend(&arrayElementCmd, argv[i], -1);
            ret = Tcl_GlobalEval(interp, aCmd);
            if (ret != TCL_OK)
            {  NXLogError("Yikes! problem in WWTCLInterp's write: (evaluating <%s> yielded <%s>)\n", 
   	               aCmd, interp->result);
               [self showError:interp->result];
               arrayElementList = NXCopyStringBuffer("");
            }
            else
            {  arrayElementList = NXCopyStringBuffer(interp->result); 
            }
            Tcl_ResetResult(interp); 
            argc2 = 0;
            argv2 = NULL;
            Tcl_SplitList(interp, arrayElementList, &argc2, &argv2);
            for (j = 0; j < argc2; j++)
            {  cnt++;
            }
            Tcl_ResetResult(interp); 
            if (argv2) {  free(argv2); }
            free(arrayElementList);  // it was malloc'ed either way
         }
      }
   }
   free(varNameList);
   varNameList = NULL;

   // we now check to see if these strings are going to put us over the limit
   // on our tmp string array.  If they will, realloc.
   while ((tmpStringIndex + cnt) > tmpStringc)
   {  tmpStringc *= 2;
      tmpStringv = (char **)NXZoneRealloc([self zone], tmpStringv, (sizeof(char *) * tmpStringc));
   }

   NXPrintf(stream, "# Writing out all the current values of the global variables:\n");
   for (i = 0; i < argc; i++)
   {  skip = NO;
      if (!strcmp(argv[i], "env")) {  skip = YES;  }
      if (!strcmp(argv[i], "PI")) {  skip = YES;  }
      if (!strcmp(argv[i], "ticksPerSecond")) {  skip = YES;  }
      if (!strcmp(argv[i], "errorInfo")) {  skip = YES;  }
      if (!strcmp(argv[i], "scene")) {  skip = YES;  }
      if (!skip)
      {  Tcl_DStringInit(&setCmd);
         Tcl_DStringAppend(&setCmd, "set ", -1);
         Tcl_DStringAppend(&setCmd, argv[i], -1);
         varValue = Tcl_GetVar(interp, argv[i], TCL_GLOBAL_ONLY);
         if (varValue)
         {  Tcl_DStringAppend(&setCmd, " {", -1);
            Tcl_DStringAppend(&setCmd, varValue, -1);
            aCmd = tmpStringv[tmpStringIndex++] = NXCopyStringBuffer(Tcl_DStringAppend(&setCmd, "}", -1));
            NXPrintf(stream, "%s;\n", aCmd);
            Tcl_DStringFree(&setCmd);
         }
         else
         {  // if we failed to get a value for the var, it must be an array.
            // need to find out how many elements there are and save out each one.
            // eval "array names <var>", do a split list on it, etc.
   
            Tcl_DStringInit(&arrayElementCmd);
            Tcl_DStringAppend(&arrayElementCmd, "array names ", -1);
            aCmd = Tcl_DStringAppend(&arrayElementCmd, argv[i], -1);
            ret = Tcl_GlobalEval(interp, aCmd);
            if (ret != TCL_OK)
            {  NXLogError("Yikes! problem in WWTCLInterp's write: (evaluating <%s> yielded <%s>)\n", 
   	               aCmd, interp->result);
               [self showError:interp->result];
               arrayElementList = NXCopyStringBuffer(""); 
            }
            else
            {   arrayElementList = NXCopyStringBuffer(interp->result); 
            }
            Tcl_ResetResult(interp); 
   
            arrayName = NXCopyStringBuffer(Tcl_DStringAppend(&setCmd, "", 0));
            argc2 = 0;
            argv2 = NULL;
            Tcl_SplitList(interp, arrayElementList, &argc2, &argv2);
            for (j = 0; j < argc2; j++)
            {  // need to join the current 
               Tcl_DStringInit(&setCmd);
               Tcl_DStringAppend(&setCmd, arrayName, -1);
               Tcl_DStringAppend(&setCmd, "(", -1);
               Tcl_DStringAppend(&setCmd, argv2[j], -1);
               aCmd = Tcl_DStringAppend(&setCmd, ")", -1);
               ret = Tcl_GlobalEval(interp, aCmd);
               if (ret != TCL_OK)
               {  NXLogError("Yikes! problem in WWTCLInterp's write: (evaluating <%s> yielded <%s>)\n", 
   	                  aCmd, interp->result);
                  [self showError:interp->result];
                  aCmd = tmpStringv[tmpStringIndex++] = NXCopyStringBuffer("BAD VALUE");
               }
               else
               {  Tcl_DStringAppend(&setCmd, " {", -1);
                  Tcl_DStringAppend(&setCmd, interp->result, -1);
                  aCmd = tmpStringv[tmpStringIndex++] = NXCopyStringBuffer(Tcl_DStringAppend(&setCmd, "}", -1));
               }
               Tcl_ResetResult(interp); 
   
               NXPrintf(stream, "%s;\n", aCmd);
               Tcl_DStringFree(&setCmd);
            }
            if (arrayName)  {  free(arrayName); }
         }
      }
   }
   if (argv) {  free(argv); }
   NXPrintf(stream, "# end of globals\n");

   NXPrintf(stream, "# Writing out all the current definitions of all the non built-in procs:\n");
   ret = Tcl_GlobalEval(interp, "info procs"); 
   if (ret != TCL_OK)
   {  NXLogError("Yikes! problem in WWTCLInterp's write: (evaluating <info procs> yielded <%s>)\n", 
	         interp->result);
      [self showError:interp->result];
   }
   procNameList = NXCopyStringBuffer(interp->result); 
   Tcl_ResetResult(interp); 

   Tcl_SplitList(interp, procNameList, &argc, &argv);

   // we now check to see if these strings are going to put us over the limit
   // on our tmp string array.  If they will, realloc.
   while ((tmpStringIndex + cnt) > tmpStringc)
   {  tmpStringc *= 2;
      tmpStringv = (char **)NXZoneRealloc([self zone], tmpStringv, (sizeof(char *) * tmpStringc));
   }

   for (i = 0; i < argc; i++)
   {  Tcl_DStringInit(&procCmd);
      Tcl_DStringAppend(&procCmd, "proc ", -1);
      Tcl_DStringAppend(&procCmd, argv[i], -1);
      Tcl_DStringAppend(&procCmd, " { ", -1);

      Tcl_DStringInit(&infoCmd);
      Tcl_DStringAppend(&infoCmd, "info args ", -1);
      aCmd = Tcl_DStringAppend(&infoCmd, argv[i], -1);
      ret = Tcl_GlobalEval(interp, aCmd);
      if (ret != TCL_OK)
      {  NXLogError("Yikes! problem in WWTCLInterp's write: (evaluating <%s> yielded <%s>)\n", 
	            aCmd, interp->result);
         [self showError:interp->result];
         Tcl_ResetResult(interp); 
         aCmd = tmpStringv[tmpStringIndex++] = NXCopyStringBuffer(Tcl_DStringAppend(&procCmd, 
                                                                                    "} {echo {this proc had bad args\\n} }", -1));
      }
      else
      {  Tcl_DStringAppend(&procCmd, interp->result, -1);
         Tcl_DStringAppend(&procCmd, " } { ", -1);
         Tcl_ResetResult(interp); 
         Tcl_DStringFree(&infoCmd);

         Tcl_DStringInit(&infoCmd);
         Tcl_DStringAppend(&infoCmd, "info body ", -1);
         aCmd = Tcl_DStringAppend(&infoCmd, argv[i], -1);
         ret = Tcl_GlobalEval(interp, aCmd);
         if (ret != TCL_OK)
         {  NXLogError("Yikes! problem in WWTCLInterp's write: (evaluating <%s> yielded <%s>)\n", 
   	               aCmd, interp->result);
            [self showError:interp->result];
            aCmd = tmpStringv[tmpStringIndex++] = NXCopyStringBuffer(Tcl_DStringAppend(&procCmd, 
                                                                                    "echo {this proc had a bad body\\n} }", -1));
         }
         else
         {  Tcl_DStringAppend(&procCmd, interp->result, -1);
            Tcl_DStringFree(&infoCmd);

            aCmd = tmpStringv[tmpStringIndex++] = NXCopyStringBuffer(Tcl_DStringAppend(&procCmd, " }", -1));
         }
         Tcl_ResetResult(interp); 
      }
      //NXLogError("writing out: <%s> at address %d\n", aCmd, aCmd);
      NXPrintf(stream, "%s;\n", aCmd);
      Tcl_DStringFree(&procCmd);

   }
   NXPrintf(stream, "# end of procs\n");
   if (argv) {  free(argv); }
   if (procNameList) {  free(procNameList); }

   return self;
}

#define typeVector "@"
#define typeValues &wwVarList

- read:(NXTypedStream*)stream 
{
    int version;
    [super read:stream];
NX_DURING
    version = NXTypedStreamClassVersion(stream,"WWInterp");
    if (version == 0) NXReadTypes(stream,"i",&version), version=1;
    if (version == 1)
    {  NXReadTypes(stream, typeVector, typeValues);
    } 
NX_HANDLER
   NXLogError("in read: %s, exception [%d] raised.\n", 
                 [[self class] name], NXLocalHandler.code);
   return nil;
NX_ENDHANDLER
    return self;
}

- write:(NXTypedStream*)stream 
{
    [super write:stream];

    NXWriteTypes(stream, typeVector, typeValues);

    return self;
}


@end
